package HierarchyPackage.SimpleHierarchy;

import HierarchyPackage.*;
import ClassifiedDataSetPackage.ClassifiedDataSet;
import DataSetPackage.View;
import OrderedCoverPackage.SC;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutorService;

/*
 * @author Martin Haulrich
 */
/**
 * Hierarchy-class for simple hierarchies.
 * Simple hierarchies are hierarchies where a class is represented by a string.
 * The hierachy-structure is either added manualle or read from a file.
 */
public class SimpleHierarchyMem extends AbstractSimpleHierarchy implements HierarchyThreaded {

    /**
     * JDBM HashTable for mapping from unique classIDs to class name
     */
    Map<Integer, String> idToName;

    /**
     * JDBM HashTable containing a Map from a class' name to its ID
     */
    Map<String, Integer> nameToID;

    /**
     * JDBM HashTable for mapping from a class' ID to a array containing the
     * IDs of its superclasses
     */
    Map<Integer, Set<Integer>> superclassesTab;

    public void p() {
//System.err.println(top.getID());
        System.err.println(top.getName() + "\t" + "1");
        Set<Integer> children = new HashSet<Integer>();
        for (int l : getAllClasses()) {
            if (l != 0) {
//                System.err.println(l + "  " + getHClass(l).getName());
//                System.err.println(superclassesTab);
                if ((!superclassesTab.containsKey(l)) || superclassesTab.get(l).contains(top.getID())) {
                    children.add(l);
                }
            }
        }
        double score = (double) 1 / children.size();
        for (Integer ch : children) {
            recp(ch, score);
        }

    }

    private void recp(int id, double s) {
        System.err.println(getHClass(id).getName() + "\t" + s);
        Set<Integer> children = new HashSet<Integer>();
        for (int l : getAllClasses()) {
            if (l != 0) {
                if ((superclassesTab.containsKey(l)) && superclassesTab.get(l).contains(id)) {
                    children.add(l);
                }
            }
        }
        double score = (double) s / children.size();
        for (Integer ch : children) {
            recp(ch, score);
        }
    }

    /**
     * Constructor.
     * Constructs a new non-disjunctive hierarchy.
     * Thrown if the hierarchy cannot be created.
     */
    public SimpleHierarchyMem() {
        this(false);
    }

    /**
     * Constructor.
     * Construct a new hierarchy.
     * @param disjunctive Flag to tell whether or not the hierarchy should be disjunctive.
     * Thrown if the hierarchy cannot be created.
     */
    public SimpleHierarchyMem(boolean disjunctive) {

        this.disjunctive = disjunctive;
        top = new SimpleHClass(this, 0);
        classCounter = 0;


        // Create idToName hash-table and add TOP as element;
        idToName = new HashMap<Integer, String>();
        idToName.put(classCounter, "TOP");


        // Create idToName hash-table and add TOP as element;
        nameToID = new HashMap<String, Integer>();
        nameToID.put("TOP", classCounter);

        //// Create superclassTable and add empty array as parents to TOP.
        superclassesTab = new HashMap<Integer, Set<Integer>>();



        classCounter++;



    }

    /**
     * Constructor.
     * Create a hierarchy and load its structure from file.
     * File has the following format;<br>
     * * First line indicates hierarchy type, for instance:<br>
     * <code>Tree-hierarchy</code><br>
     * * Second line tells whether the hierarchy is disjunctive:<br>
     * <code>non-disjunctive</code>
     * * Following lines contains the hierarchy itself:<br>
     *     * <code> subclass: superclass </code>
     * @param name Name for the file containing the hierarchy-structure.
     * Thrown if the hierarchy cannot be created.
     */
    public SimpleHierarchyMem(String name) {
        this();
        try {
            disjunctive = load2(name);
        } catch (IOException ex) {
            ex.printStackTrace();
            throw new HierarchyException("Could not load hierarchy " + ex.getMessage());
        }

    }

    @Override
    String getName(int classID) {

        if (classExists(classID)) {


            return idToName.get(classID);

        } else {
            throw new HierarchyException("Class with ID: " + classID + " does not exist.");
        }
    }

    @Override
    synchronized int getID(String className) {

        if (classExists(className)) {

            return nameToID.get(className);
        } else {
            throw new HierarchyException("Class with name: " + className + " does not exist");
        }
    }

    @Override
    void addSuperclass(int superclassID, int classID) {

        //System.err.println("addSuperclass(long,long). superclassID: " + superclassID + "  classID:" + classID);

        if (classExists(superclassID) && classExists(classID)) {

            // Do not add TOP in list of superclasses
            if (superclassID != 0) {

                // Retrieve existing superclasses
                // If we use getSuperclasses and classID currently has no
                // superclasses we will receive an arary containing 0 (TOP)
                // this is unpractical and therefore we acces the table directly
                int[] superclasses;
                if (superclassesTab.containsKey(classID)) {
                    Set<Integer> supSet = superclassesTab.get(classID);
                    Integer[] supInteger = supSet.toArray(new Integer[0]);
                    superclasses = new int[supInteger.length];
                    for (int i = 0; i < supInteger.length; i++) {
                        superclasses[i] = supInteger[i];
                    }
                } else {
                    superclasses = new int[0];
                }


                // Create new array for superclasses
                int[] newSuperclasses = new int[superclasses.length + 1];
                System.arraycopy(superclasses, 0, newSuperclasses, 0, superclasses.length);

                // add new class in superclass array and save in table
                newSuperclasses[newSuperclasses.length - 1] = superclassID;


                Set<Integer> newSup = new HashSet<Integer>();
                for (int i = 0; i < newSuperclasses.length; i++) {
                    newSup.add(newSuperclasses[i]);
                }

                superclassesTab.put(classID, newSup);

            }
        } else {
            throw new HierarchyException("Class with ID: " + superclassID + " or " + classID + " does not exist");
        }
    }

    @Override
    synchronized boolean classExists(int classID) {
        return idToName.containsKey(classID);
    }

    @Override
    synchronized boolean classExists(String className) {
        return nameToID.containsKey(className);
    }

    @Override
    int addClass(String className, int[] superclasses) {

        int classID;
        if (classExists(className)) {
            classID = getID(className);
        } else {
            // Add new class to list of classes
            classID = classCounter;
            classCounter++;

            idToName.put(classID, className);
            nameToID.put(className, classID);

        }

        for (int sc : superclasses) {
            addSuperclass(sc, classID);
        }



        return classID;
    }

    @Override
    synchronized int[] getSuperclasses(int classID) {

        if (classExists(classID)) {

            int[] superclasses;
            if (superclassesTab.containsKey(classID)) {
//                Set<Long> supSet = superclassesTab.get(classID);
//                Long[] supLong = supSet.toArray(new Long[0]);
//                superclasses = new long[supLong.length];
//
//
//                for (int i=0; i<supLong.length; i++) {
//                    superclasses[i] = supLong[i].longValue();
//                }
                superclasses = new int[superclassesTab.get(classID).size()];
                int i = 0;
                for (Iterator<Integer> it = superclassesTab.get(classID).iterator(); it.hasNext();) {
                    Integer ID = it.next();
                    superclasses[i] = ID;
                    i++;

                }
            } else {
                if (classID == 0) {
                    return new int[0];
                } else {
                    int[] TOParray = {0};
                    return TOParray;
                }
            }
            return superclasses;

        } else {
            throw new HierarchyException("Class with ID: " + classID + " does not exist.");
        }
    }

    @Override
    public int[] getAllClasses() {

        Set<Integer> keySet = idToName.keySet();
        Integer[] keyInteger = keySet.toArray(new Integer[0]);
        int[] classes = new int[keyInteger.length];
        for (int i = 0; i < keyInteger.length; i++) {
            classes[i] = keyInteger[i];
        }
        int[] onlyTOP = {0};
        if (Arrays.equals(classes, onlyTOP)) {
            return new int[0];
        } else {
            return classes;
        }

    }

    @Override
    public ClassifiedDataSet getClassifications(Collection<Integer> dids, View v, HClass topClass,
            int maxDepth, int minCount, int cutFraction, boolean saveData, ExecutorService pool) {

        throw new UnsupportedOperationException("Threaded is no longer supported becuase we do not have the split operation any more - simply implemt slit on the collection");
//
//
//        /**
//         * @TODO minCount currently not in use
//         */
//        ClassifiedDataSetStorage result = new ClassifiedDataSetStorage();
//
//        if ((topClass instanceof SimpleHClass) && (v instanceof SimpleView)) {
//
//            SimpleHClass tC = (SimpleHClass) topClass;
//            SimpleView dv = (SimpleView) v;
//
//            int maxThreads = 8;
//            DataIDs[] dd = dids.Split(maxThreads);
//
//            Future[] ff = new Future[maxThreads];
//            Set<Integer> running = new HashSet<Integer>();
//
//            for (int i = 0; i < maxThreads; i++) {
//
//                ff[i] = pool.submit(new SimpleThread(dd[i], dv, tC, maxDepth, result));
//                running.add(i);
//            }
//
//            while (!running.isEmpty()) {
//                try {
//                    Thread.sleep(100);
//                } catch (InterruptedException ex) {
//                    ex.printStackTrace();
//                }
//                for (int i = 0; i < maxThreads; i++) {
//                    if (ff[i].isDone()) {
//                        running.remove(i);
//                    }
//                }
//            }
//
//        }
//        return result;
    }

    private SimpleHClass getClass(String name) {

        return new SimpleHClass(this, getID(name));
    }

    private void save(String filename) throws IOException {

        File f = new File(filename);
        if (f.exists()) {
            throw new IOException("Hierarchy with name: '" + filename + "' already exists - cannot overwrite");
        }

        String treeStr = toStringParam(false, true);

        PrintWriter out = new PrintWriter(new FileWriter(filename));

        out.println(hierarchyType + newline);

        out.println(treeStr);

        out.close();
    }

    @Override
    public Map<HClass, Integer> saveEst(String filename, Set<HClass> classes) throws IOException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Map<Integer, HClass> loadEst(String filename, Set<Integer> classIDs) throws IOException {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public HClass newClass(Integer did, View v) {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    public ClassifiedDataSet getClassifications(Collection<Integer> dids, View dv, HClass topClass, int maxDepth, int minCount, int cutFraction, boolean saveData, SC parentClasses,  int startdim) {
        throw new UnsupportedOperationException("Not supported yet.");
    }
}
